《windows PE》导入表

第一部分 预备知识

1.函数导入流程

         程序在引用dll库函数的时候,需要从dll里面对所需要的函数进行导入,该导入过程如下:

  • 1.将库函数所需要的参数进行压栈。
  • 2.call一个近地址的用户领空的函数。
  • 3.jmp XXXX到dll内部的系统领空。

2.计算RVA对应的FOA

  • 查看RVA所落在的节区
  • 计算偏移offset
  • 计算在文件中的偏移
    使用工具(PE或者exeinfope)

3.代码导入

         程序需要执行dll相关代码,则相关代码指令必须存在于进程地址空间,也就是说,操作系统在加载的时候会根据导入表的描述将需要调用的函数指令加载到进程空间。但是系统不会重复加载同一个dll库,如果多个进程需要使用同一个dll,通过页面调度机制使得两个进程访问同一个动态数据库。

第二部分 PE导入表

1.导入表如何定位

  • 第一步:利用Pe_View,在IMAGE_OPTIONAL_HEADER中DataDirectotion中的IMPORT Table。
  • 第二歩:利用PEID查看文件各个区段的内存偏移和文件偏移,如上图,IMPORTTable的RVA是5000,通过查看PEID,发现位置正好落在idate段,所以,在文件中的偏移是1400

2.导入表描述符IMAGE_IMPORT_DESCRIPTOR

         导入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组。导入表数据的起始部分是多组导入表描述符结构。每个结构包含PE文件引入函数的一个相关DLL的信息。比如,如果该PE文件从10个不同的DLL中引入函数,那么这个数组就有10个成员。该数组以一个全0的成员结尾。一下是他的数据结构

1
2
3
4
5
6
7
8
9
10
IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //INT(导入名字表)的地址(RVA)桥1
};
DWORD TimeDateStamp; //时间戳
DWORD ForwarderChain; //链表的前一个结构
DWORD Name; //指向链接库的指针
DWORD FirstThunk; //(IAT)导入地址表的地址 (RVA)桥2
} IMAGE_IMPORT_DESCRIPTOR;

3.导入表的双桥结构

         每一个结构IMAGE_IMPORT_DESCRIPTOR都对应的是一个唯一的dll文件。以及dll中的每个函数都可以通过”编号-名称”的方式找到,这就是导入表的双桥结构。如下图尽管这个桥对应的INT和IAT的内容是相同的,但是其存储的位置是不同的。

         OriginalFirstThun指向的数组中每一项为一个结构,此结构的名称是IMAGE_THUNK_DATA。该结构实际上只是一个双字,但在不同的时刻却拥有不同的解释。该字段有两种解释:这个值是INT的地址,INT(Import Name Table)是一个存储了库文件函数名称的表。在装载的时候,PE装载器读取OriginalFirstThun获得INT,然后通过函数名称去获取函数的地址

  • 双字最高位为0,表示导入符号是一个数值,该数值是一个RVA。
  • 双字最高位为1,表示导入符号是一个名称。
    1
    2
    3
    4
    typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD Hint; //对dll中的每个函数进行标号,该值不是必须的
    BYTE Name[1]; //函数名称
    } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

         FirstThunk指向的是IAT表,也是IMAGE_THUNK_DATA,但是对于程序的装载,并不使用IAT进行函数的寻址,在通过INT寻址之后,将找到的地址填充到IAT中。后期需要用到函数的时候,可以使用GetProcAddressAPI函数进行获取。

4.导入函数地址表(IAT)

         导入函数地址表位于数据目录的第十三个目录。用户程序通过该表的jmp指令无条件跳转到dll函数的VA处。因为该表中存着同一个dll不同的函数的VA地址,所以每个函数地址都是以”00”作为区分的。
         导入表和导入地址表是由紧密联系的。通过桥2可以定位到程序的IAT。在内存中,桥1可以找到调用函数的名称和函数的标号。桥2可以找到该函数指令代码在内存空间的地址

5.程序的装载过程。

  • 第一步:PE加载器读取结构体成员的值,IMAGE_IMPORT_DESCRIPTOR.Name成员找到库名称,然后将库文件加载到内存中来。
  • 第二歩:PE加载器读取OriginalFirstThunk值获得INT地址,然后依次读取INT各项的值,根据函数的标号获取函数的地址。
  • 第三步:将获取的函数的地址填充到IAT表中。

第三部分:绑定导入

1.绑定导入机制

         windows在装载程序的时候,PE装载器负责对IAT中的地址进行修正工作。绑定导入的目的在于使用软件或者人工的方法,在程序装载之前,修正IAT表内的函数地址,从而提高加载数据。同样的,微软在引入绑定导入机制也考虑到了由于各种原因导致dll没有加载到目标地址,所以同时引入了错误检测机制,如果检测到此类错误,PE加载器则会接管IAT修正工作。加载器遍历INT,计算新的函数地址,由于同时要用道INT和IAT,所以:单桥结构无法使用静态绑定机制。

2.绑定导入表的定位和其数据结构

         绑定地址表位于数据目录的第十二的项目,利用RVA-FOA转化,可以定位到文件在绑定导入表的地址。
         绑定导入数据由IMAGE_BOUND_IMPORT_DESCRIPTOR结构组成,每个模块拥有一个该结构。

1
2
3
4
5
6
IMAGE_BOUND_IMPORT_DESCRIPTOR struct
{
TimeDateStamp ;时间戳
OffsetDllNmae ;指向dll'的名称,
NumberOfModuleForWarderRefs ;ModuleForWarderRefs数目
}